package BC;


import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.misc.MiscObjectIdentifiers;
import org.bouncycastle.asn1.misc.NetscapeCertType;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.X509KeyUsage;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;

import java.io.IOException;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.*;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.spec.ECGenParameterSpec;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;

import static org.bouncycastle.asn1.x509.Extension.extendedKeyUsage;


public class SM2Util {
    //BouncyCastle算法提供者
    private static final Provider BC = new BouncyCastleProvider();
    /**
     * SM2加密算法
     //@param publicKey     公钥
     // @param data          明文数据
     * @return
     */
    public KeyPairGenerator generator() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
        // 获取SM2椭圆曲线的参数
        final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
        // 获取一个椭圆曲线类型的密钥对生成器
        final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
        // 使用SM2参数初始化生成器
        kpg.initialize(sm2Spec);
        return kpg;
    }
    public String encrypt(PublicKey publicKey, String data) {

        ECPublicKeyParameters ecPublicKeyParameters = null;
        if (publicKey instanceof BCECPublicKey) {
            BCECPublicKey bcecPublicKey = (BCECPublicKey) publicKey;
            ECParameterSpec ecParameterSpec = bcecPublicKey.getParameters();
            ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
                    ecParameterSpec.getG(), ecParameterSpec.getN());
            ecPublicKeyParameters = new ECPublicKeyParameters(bcecPublicKey.getQ(), ecDomainParameters);
        }

        SM2Engine sm2Engine = new SM2Engine();
        sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom()));

        byte[] arrayOfBytes = null;
        try {
            byte[] in = data.getBytes("utf-8");
            arrayOfBytes = sm2Engine.processBlock(in,0, in.length);
        } catch (Exception e) {
            System.out.println("SM2加密时出现异常:");
        }
        return Hex.toHexString(arrayOfBytes);
    }

    /**
     * SM2解密算法
     * @param privateKey        私钥
     * @param cipherData        密文数据
     * @return
     */
    public String decrypt(PrivateKey privateKey, String cipherData) {
        byte[] cipherDataByte = Hex.decode(cipherData);

        BCECPrivateKey bcecPrivateKey = (BCECPrivateKey) privateKey;
        ECParameterSpec ecParameterSpec = bcecPrivateKey.getParameters();

        ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
                ecParameterSpec.getG(), ecParameterSpec.getN());

        ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(bcecPrivateKey.getD(),
                ecDomainParameters);

        SM2Engine sm2Engine = new SM2Engine();
        sm2Engine.init(false, ecPrivateKeyParameters);

        String result = null;
        try {
            byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length);
            return new String(arrayOfBytes, "utf-8");
        } catch (Exception e) {
            System.out.println("SM2解密时出现异常");
        }
        return result;
    }
    public byte[] sign(PrivateKey privateKey, byte[] plainText) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {
        // 生成SM2sign with sm3 签名验签算法实例
        Signature signature = Signature.getInstance("SM3withSm2", new BouncyCastleProvider());
        signature.initSign(privateKey);
        signature.update(plainText);
        byte[] signatureValue = signature.sign();
        return signatureValue;
    }

    public boolean verify(PublicKey publicKey, byte[] plainText,byte[] signatureValue) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {
        // 生成SM2sign with sm3 签名验签算法实例
        Signature signature = Signature.getInstance("SM3withSm2", new BouncyCastleProvider());
        signature.initVerify(publicKey);
        signature.update(plainText);
        return signature.verify(signatureValue);
    }
    /**
     * 获取DN(Distinct Name)构造者<br>来唯一标识一个实体，其功能类似我们平常使用的ID
     * @return X500NameBuilder
     */
    public static X500NameBuilder createStdBuilder() {
        X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
        // 国家代码
        builder.addRDN(BCStyle.C, "CN");
        // 组织
        builder.addRDN(BCStyle.O, "Besti");
        // 省份
        builder.addRDN(BCStyle.ST, "Beijing");
        // 地区
        builder.addRDN(BCStyle.L, "Beijing");

        return builder;
    }

    /**
     * 获取扩展密钥用途
     */
    public static DERSequence extendedKeyUsage() {
//        // 客户端身份认证
//        ASN1ObjectIdentifier clientAuth = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.3.2");
//        // 安全电子邮件
//        ASN1ObjectIdentifier emailProtection = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.3.4");
        // 构造容器对象
        ASN1EncodableVector vector = new ASN1EncodableVector();
        // 客户端身份认证
        vector.add(KeyPurposeId.id_kp_clientAuth);
        // 安全电子邮件
        vector.add(KeyPurposeId.id_kp_emailProtection);
        return new DERSequence(vector);
    }

    /**
     * 生成证书文件
     *
     * @param x509Certificate X.509格式证书
     * @param savePath        证书保存路径
     * @throws CertificateEncodingException
     * @throws IOException
     */
    public static void makeCertFile(X509Certificate x509Certificate, Path savePath) throws CertificateEncodingException, IOException {
        if (Files.exists(savePath)) {
            // 删除已存在文件
            Files.deleteIfExists(savePath);
        }
        // 创建文件
        Files.createFile(savePath);

        // 获取ASN.1编码的证书字节码
        byte[] asn1BinCert = x509Certificate.getEncoded();
        // 编码为BASE64 便于传输
        byte[] base64EncodedCert = Base64.encode(asn1BinCert);
        // 写入文件
        Files.write(savePath, base64EncodedCert);
    }

    /**
     * 签发SM2证书
     */
    public void cert(KeyPair keyPair,String certName) throws IOException, CertificateException, OperatorCreationException {
        // 证书签名实现类
        ContentSigner sigGen = new JcaContentSignerBuilder("SM3withSM2").setProvider(BC).build(keyPair.getPrivate());
        // 构造X.509 第3版的证书构建者
        X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(
                // 颁发者信息
                SM2Util.createStdBuilder().build()
                // 证书序列号
                , BigInteger.valueOf(Instant.now().toEpochMilli())
                // 证书生效日期
                , Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant())
                // 证书失效日期
                , Date.from(LocalDateTime.now().plusMonths(12).atZone(ZoneId.systemDefault()).toInstant())
                // 使用者信息（PS：由于是自签证书，所以颁发者和使用者DN都相同）
                , SM2Util.createStdBuilder().build()
                // 证书公钥
                , keyPair.getPublic())
                //设置证书扩展
                // 设置密钥用法
                .addExtension(Extension.keyUsage, false
                        , new X509KeyUsage(X509KeyUsage.digitalSignature | X509KeyUsage.nonRepudiation))
                // 设置扩展密钥用法：客户端身份认证、安全电子邮件
                .addExtension(extendedKeyUsage, false, extendedKeyUsage())
                // 基础约束,标识是否是CA证书，这里false标识为实体证书
                .addExtension(Extension.basicConstraints, false, new BasicConstraints(false))
                // Netscape Cert Type SSL客户端身份认证
                .addExtension(MiscObjectIdentifiers.netscapeCertType, false, new NetscapeCertType(NetscapeCertType.sslClient));

        // 将证书构造参数装换为X.509证书对象
        X509Certificate certificate = new JcaX509CertificateConverter()
                .setProvider(BC)
                .getCertificate(certGen.build(sigGen));
        // 保存为证书文件
        SM2Util.makeCertFile(certificate, Paths.get(certName));

    }

}